/*******************************************************************************
 * Copyright (c) 2007, Geophysical Center RAS
 * Sergey Berezin, Dmitry Voitsekhovskiy
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms,
 * with or without modification, are permitted provided
 * that the following conditions are met:
 *
 * - Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 *
 * - Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * - Neither the name of the Geophysical Center RAS nor the names
 * of its contributors may be used to endorse or promote products
 * derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
 * THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ******************************************************************************/
using System;
using System.Collections.Generic;
using System.Text;

namespace VisualEsse.DataEngine
{
    /// <summary>
    /// Specifies a time context: min and max values, steps and so on.
    /// </summary>
    public interface ITimeContext
    {
        /// <summary>
        /// Minimal date/time for the context.
        /// </summary>
        DateTime TimeMin { get; }

        /// <summary>
        /// Maximal date/time for the context.
        /// </summary>
        DateTime TimeMax { get; }

        /// <summary>
        /// Time step for the context.
        /// </summary>
        TimeSpan TimeStep { get; }
    }

    /// <summary>
    /// CompositeTimeContext class is a union of other ITimeContext objects.
    /// </summary>
    public class CompositeTimeContext : ITimeContext
    {
        protected List<ITimeContext> contexts = new List<ITimeContext>();
        protected DateTime min;
        protected DateTime max;
        protected TimeSpan step;

        public CompositeTimeContext()
        {
            CalculateStepMinMax();
        }

        public CompositeTimeContext(ITimeContext context)
        {
            contexts.Add(context);
            CalculateStepMinMax();    
        }

        public void AddContext(ITimeContext context)
        {
            if (contexts.Count == 0)
            {
                min = context.TimeMin;
                max = context.TimeMax;
                step = context.TimeStep;
                contexts.Add(context);
                return;
            }

            contexts.Add(context);
            if (min > context.TimeMin)
                min = context.TimeMin;
            if (max < context.TimeMax)         
                max = context.TimeMax;
            if (step > context.TimeStep)
                step = context.TimeStep;
        }

        public void RemoveContext(ITimeContext context)
        {
            int i = contexts.IndexOf(context);
            if (i >= 0)
            {
                contexts.RemoveAt(i);
                CalculateStepMinMax();
            }
        }

        public void Update()
        {
            CalculateStepMinMax();
        }

        protected void CalculateStepMinMax()
        {
            if (contexts.Count == 0)
            {
                min = DateTime.MinValue;
                max = DateTime.MaxValue;
                step = new TimeSpan(1, 0, 0);
                return;
            } 

            min = contexts[0].TimeMin;
            max = contexts[0].TimeMax;
            step = contexts[0].TimeStep;
            for (int i = 1; i < contexts.Count; i++)
            {
                if (min > contexts[i].TimeMin)
                    min = contexts[i].TimeMin;
                if (max < contexts[i].TimeMax)
                    max = contexts[i].TimeMax;

                // TODO: Make a complex logic here for a time step calculation
                if (step > contexts[i].TimeStep)
                    step = contexts[i].TimeStep;
            }
        }


        protected DateTime GetNearestTime(DateTime value)
        {
            DateTime date = value.Date;
            double totalSeconds = value.TimeOfDay.TotalSeconds;
            totalSeconds = Math.Ceiling(totalSeconds / TimeStep.TotalSeconds) *
                TimeStep.TotalSeconds;
            if (totalSeconds > 24 * 60 * 60)
            {
                date.AddDays(1);
                totalSeconds = 0;
            }

            return date.AddSeconds(totalSeconds);
        }

        #region ITimeContext Members

        public DateTime TimeMin
        {
            get { return min; }
        }

        public DateTime TimeMax
        {
            get { return max; }
        }

        public TimeSpan TimeStep
        {
            get { return step; }
        }

        #endregion
    }

    public static class TimeContextUtils
    {
        public static DateTime GetNearestTime(ITimeContext timeContext, DateTime value)
        {
            DateTime date = value.Date;
            double totalSeconds = value.TimeOfDay.TotalSeconds;
            totalSeconds = Math.Round(totalSeconds / timeContext.TimeStep.TotalSeconds, MidpointRounding.AwayFromZero) *
                timeContext.TimeStep.TotalSeconds;
            if (totalSeconds > 24 * 60 * 60)
            {
                date.AddDays(1);
                totalSeconds = 0;
            }

            date = date.AddSeconds(totalSeconds);
            if (date > timeContext.TimeMax)
                date = GetNearestTime(timeContext, timeContext.TimeMax.Subtract(new TimeSpan(timeContext.TimeStep.Ticks >> 1)));
            else if (date < timeContext.TimeMin)
                date = GetNearestTime(timeContext, timeContext.TimeMax.Add(new TimeSpan(timeContext.TimeStep.Ticks >> 1)));

            return date;
        }
    }
}
